﻿const fs = require('fs');
const parser    = require("@babel/parser");
const traverse  = require("@babel/traverse").default;
const types     = require("@babel/types");
const generator = require("@babel/generator").default;

//将源代码解析为AST
process.argv.length > 2 ? encodeFile = process.argv[2]: encodeFile ="./iqiyi.js";
process.argv.length > 3 ? decodeFile = process.argv[3]: decodeFile ="./decode_result.js";

let sourceCode = fs.readFileSync(encodeFile, {encoding: "utf-8"});
let ast    = parser.parse(sourceCode);


const ForToWhileStates = 
{
	ForStatement(path)
	{
		let {init,test,update,body} = path.node;
		if (init != null || update != null)
		{
			return;
		}
		let WhileNodes = types.WhileStatement(test, body);
		path.replaceWithMultiple(WhileNodes);
	}
}

traverse(ast, ForToWhileStates);

const standardLoop = 
{
	"ForStatement|WhileStatement"({node})
	{
		if(!types.isBlockStatement(node.body))
    {
    	node.body = types.BlockStatement([node.body]);
    }
  },
}

traverse(ast, standardLoop);



function isSequenceCanBeResolved(path)
{
	let parentPath = path.parentPath;
	if (parentPath.isForStatement() || parentPath.isLogicalExpression())
	{
		return false;
	}
	let statement = path.getStatementParent();
	if (!statement) 
	{
		return false;
	}
	let CondintionPath = path.findParent(p => p.isConditionalExpression());
	if (CondintionPath)
	{
		let nextCondintionPath = statement.findParent(p => p.isConditionalExpression());
		if (nextCondintionPath != CondintionPath) 
		{
			return false;
		}
	}	
	return true;
}


//处理逗号表达式
const resolveSequence = {
	SequenceExpression: {
		exit(path){
			let {scope} = path;
			if (!isSequenceCanBeResolved(path))
			{
				return;
			}
			let statement = path.getStatementParent();
			let expressions = path.get('expressions');
			let lastExpression = expressions.pop();
			for (let expression of expressions)
			{
				if(expression.isLiteral() || expression.isIdentifier())
				{
					expression.remove();
					continue;
				}
				statement.insertBefore(types.ExpressionStatement(expression=expression.node));
			}
			path.replaceInline(lastExpression);
			scope.crawl();
    }
    
  },
}

traverse(ast, resolveSequence);



const changeNextValue = 
{
	AssignmentExpression(path)
	{
		let {left,operator,right} = path.node;
		if (operator != "+=" || !types.isIdentifier(left,{name:"w"}))
		{
			return;
		}
		
		let casePath = path.findParent(p => p.isSwitchCase());
		if (!casePath) return;
		
		let {test} = casePath.node;
		let caseValue = test.value;
		
		path.node.operator = "="
		if (types.isNumericLiteral(right))
		{
			path.node.right = types.valueToNode(caseValue + right.value);
		}
		else if (types.isUnaryExpression(right))
		{
			path.node.right = types.valueToNode(caseValue - right.argument.value);
		}
		else if (types.isConditionalExpression(right))
		{
			let {consequent,alternate} = right;
			if (types.isNumericLiteral(consequent))
			{
				path.node.right.consequent = types.valueToNode(caseValue + consequent.value);
			}
			else if (types.isUnaryExpression(consequent))
			{
				path.node.right.consequent = types.valueToNode(caseValue - consequent.argument.value);
			}			
			else
			{
				console.log(666)
			}
			if (types.isNumericLiteral(alternate))
			{
				path.node.right.alternate = types.valueToNode(caseValue + alternate.value);
			}	
			else if (types.isUnaryExpression(alternate))
			{
				path.node.right.alternate = types.valueToNode(caseValue - alternate.argument.value);
			}					
			else
			{
				console.log(generator(right).code)
				console.log(777777777777)
			}
			
		}
	},
}

traverse(ast, changeNextValue);


const changeConditional = 
{
	ConditionalExpression(path)
	{
		let {test,consequent,alternate} = path.node;
		if (!types.isAssignmentExpression(consequent) ||
		    !types.isAssignmentExpression(alternate))
		{
			return;
		}
		let leftValue,rightValue;
		do
		{
			let {left,operator,right} = consequent;
			if (!types.isIdentifier(left,{name:"w"}))
			{
				return;
			}
			leftValue = right;
		}while(0)
		do
		{
			let {left,operator,right} = alternate;
			if (!types.isIdentifier(left,{name:"w"}))
			{
				return;
			}
			rightValue = right;
		}while(0)	
		
		let newConditionalNode = types.ConditionalExpression(test,leftValue,rightValue);
		let newAssignmentNode  = types.AssignmentExpression("=",types.Identifier("w"),newConditionalNode);
		path.replaceWith(newAssignmentNode);
	}
}

traverse(ast, changeConditional);

const constantFold = 
{
	  "BinaryExpression|UnaryExpression|ConditionalExpression|CallExpression"(path)
    {
    	if(path.isUnaryExpression({operator:"-"}) || path.isUnaryExpression({operator:"void"}))
    	{
    		return;
    	}
    	const {confident,value} = path.evaluate();
    	if (value == "Infinity" || !confident) return;
    	path.replaceInline(types.valueToNode(value));
    },
}


function getItems(switchNodes,initValue)
{
	let items = undefined;
	for (const eachCase of switchNodes.cases)
	{
		let {test,consequent} = eachCase;
		if (types.isNumericLiteral(test,{value:initValue}))
		{
			let len = consequent.length;
			if (types.isBreakStatement(consequent[len-1]))
			{
				consequent.pop();
			}
			items = consequent;
			break;
		}
	}
	return items;
}


function findNodeIndex(stack,node)
{
	let index = -1;
	for (let i=0; i<stack.length; i++)
	{
		if (stack[i][0][0] == node)
		{
			index = i;
			break;
		}
		
	}
	return index;
}


let loopValues = [[]];
let total = 0;

function resolveForSwitch(switchNodes,initName,initValue,stopValue,iselseNode)
{
	let resList = [];
	let stack   = []; 

	let nextValue = initValue;
	
	if (iselseNode == true)
	{
		loopValues[++total] = [];
	}
	
	while (nextValue != stopValue)
	{
		let items = getItems(switchNodes,nextValue);
		for (const item of items)
		{
			if (types.isExpressionStatement(item))
			{
				let expression = item.expression;
				if (types.isAssignmentExpression(expression))
				{
					let {left,right} = expression;
					if (types.isIdentifier(left,{name:initName}))
					{
						if (types.isNumericLiteral(right))
						{
							nextValue = right.value;
							while (true)
							{
								let lastIndex   = stack.length-1;
								if (lastIndex == -1 ||  stack[lastIndex][1] != nextValue)
								{
									break;
								}
								let save = stack.pop();
								let nodes = save[0];
								let ifNode   = types.BlockStatement(nodes.slice(1));
								let ifStateNode = types.IfStatement(nodes[0], ifNode);
								if (stack.length > 0)
								{
									stack[stack.length-1][0].push(ifStateNode);
								}
								else
								{
									resList.push(ifStateNode);
								}
							}
						}
						else
						{
								let {test, consequent, alternate} = right;
								let ifValue   = consequent.value;
								let elseValue = alternate.value;
								if (loopValues[total].includes(ifValue))
								{
									let index = findNodeIndex(stack,test);
									if (index == -1)
									{
										return resList;
									}
									while (stack.length - 1 > index)
									{
										let save = stack.pop();
										let nodes = save[0];
										let initValue = save[1];
										let ifNode   = types.BlockStatement(nodes.slice(1));
									  let elseList = resolveForSwitch(switchNodes,initName,initValue,stopValue);
									  let elseNode = types.BlockStatement(elseList);
									  let ifStateNode = types.IfStatement(nodes[0], ifNode,elseNode);
									  if (elseList.length == 0)
									  {
									  	ifStateNode = types.IfStatement(nodes[0], ifNode);
									  }
									  stack[stack.length-1][0].push(ifStateNode);

									}
									let save = stack.pop();
									let nodes = save[0];
									nextValue = save[1];
									let WhileBody  = types.BlockStatement(nodes.slice(1));
									let WhileNodes = types.WhileStatement(test, WhileBody);
									if (stack.length > 0)
									{
										stack[stack.length-1][0].push(WhileNodes);
									}
									else
									{
										resList.push(WhileNodes);
									}
								}
								else
								{
									nextValue = ifValue;
									loopValues[total].push(ifValue);
									stack.push([[test],elseValue]);
								}
						}
						continue;
					}
				}
			}
			
			if (stack.length > 0)
			{
				stack[stack.length-1][0].push(item);
			}
			else
			{
				resList.push(item);
			}
			if (types.isReturnStatement(item))
      {
        nextValue = stopValue;
      }	
		}
	}
  while (stack.length > 0)
	{
		let save = stack.pop();
		let nodes = save[0];
		let initValue = save[1];
		let ifNode   = types.BlockStatement(nodes.slice(1));
		let elseList = resolveForSwitch(switchNodes,initName,initValue,stopValue,true);
		let elseNode = types.BlockStatement(elseList);
		let ifStateNode = types.IfStatement(nodes[0], ifNode,elseNode);
		if (elseList.length == 0)
		{
			ifStateNode = types.IfStatement(nodes[0], ifNode);
		}
		if (stack.length == 0)
		{
			resList.push(ifStateNode);
		}
		else
		{
			stack[stack.length-1][0].push(ifStateNode);
		}	
	}
	return resList;
}

const decodeControlFlow = {
	
	WhileStatement(path)
	{
		let {node,scope} = path;
		let {test,body} = node;
		
		if (!types.isBinaryExpression(test)) return;
		let {left,operator,right} = test;
		
		if (!types.isIdentifier(left,{name:"n8s"}) || operator != "!==" ||
		    !types.isNumericLiteral(right))
		{
			return;
		}
		
		let stopValue = right.value;
		let initName = left.name;
		
		const binding = scope.getBinding(initName);
		if (!binding || !binding.path || !binding.path.isVariableDeclarator()) return;
		
		let bindPath = binding.path;
		init = bindPath.node.init;
		if (!types.isNumericLiteral(init)) return;
		let initValue = init.value;

		
		body = body.body;
		if (body.length != 1 || !types.isSwitchStatement(body[0]))
		{
			return;
		}
		
		let switchNodes = body[0];
		let retBody = resolveForSwitch(switchNodes,initName,initValue,stopValue);
		path.replaceWithMultiple(retBody);
		bindPath.remove();
	},
}

traverse(ast, decodeControlFlow);

traverse(ast, constantFold);


let {code} = generator(ast,opts = {jsescOption:{"minimal":true}});
fs.writeFile(decodeFile, code, (err) => {});